/* RelationshipKeySetter.m created by cfeder on Tue 08-Nov-1994 */

#import "RelationshipKeySetter.h"
#import "ValueForKey.h"
#import <objc/objc-runtime.h>

@implementation EOEntity (attrsNamed)
- attributesNamed:(NSArray *)names
{
    int i, count = [names count];
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
    for(i=0; i<count; i++) {
        EOAttribute *attr = [self attributeNamed:[names objectAtIndex:i]];
        if (attr)
            [result addObject:attr];
    }
    return result;
}

- createObject
{
    id eo = nil;
    const char *className  = [self className];
    Class eoClass = objc_lookUpClass (className);

    if (!eoClass) eoClass = [EOGenericRecord class];

    if (eoClass) {
        eo = [eoClass allocWithZone:[self zone]];
        if ([eo respondsToSelector:@selector(initWithPrimaryKey:entity:)])
            eo = [eo initWithPrimaryKey:nil entity:self];
        else
            [eo init];
    }
    return [eo autorelease];
}
@end


@implementation EORelationship (setPropsInDestObject)
- (NSArray *)sourceKeys
{
    EORelationship *sourceRelationship = [self isFlattened]
        ? [[self componentRelationships] objectAtIndex:0] : self;
    NSArray *joins = [sourceRelationship joins];
    int i, count = [joins count];
    NSMutableArray *results = [NSMutableArray arrayWithCapacity:count];

    for (i = 0; i < count; i++) {
        EOJoin *join = [joins objectAtIndex:i];
        [results addObject:[[join sourceAttribute] name]];
    }
    return results;
}

- (void)setJoinPropertiesInDestinationObject:destination fromSourceObject:source
{
    NSArray *joins = [self joins];
    int i, count = [joins count];

    for (i = 0; i < count; i++) {
        EOJoin *join = [joins objectAtIndex:i];
        id val = [(NSObject *)source valueForKey:[[join sourceAttribute] name]];
        if (!val) val = [EONull null];
        [destination takeValue:val forKey:[[join destinationAttribute] name]];
    }
}

- (void)setJoinPropertiesInSourceObject:source fromDestinationObject:destination
{

    NSArray *joins = [self joins];
    int i, count = [joins count];

    for (i = 0; i < count; i++) {
        EOJoin *join = [joins objectAtIndex:i];
        id val = [(NSObject*)destination valueForKey:[[join destinationAttribute] name]];
        if (!val) val = [EONull null];
        [source takeValue:val forKey:[[join sourceAttribute] name]];
    }
}

- (NSDictionary *)destinationValuesFromSourceObject:sourceObject
{
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    [self setJoinPropertiesInDestinationObject:dictionary fromSourceObject:sourceObject];
    return dictionary;
}

- (BOOL)hasJoinsOnPrimaryKeyOfSourceEntity
{
    // FIX ME!
    // this is not quite right for multi-attribute keys (the ordering may
    // cause a false mismatch)
    return [[self sourceKeys] isEqual:[[self entity] primaryKeyAttributeNames]];
}

- (void)updateMasterObject:master withNewDestinationObject:destination
    // For to-one relationships!
{
    // Decide which way to propagate the attributes based on which side of the
    // relationship hooks to a primary key.
    if ([self hasJoinsOnPrimaryKeyOfSourceEntity])
        [self setJoinPropertiesInDestinationObject:destination fromSourceObject:master];
    else
        [self setJoinPropertiesInSourceObject:destination fromDestinationObject:master];
}

- linkObjectForSourceObject:master destinationObject:destination
    // created an intermediate object for many-to-many relationship through
    // an intermediate table.  Initializes keys of intermediate object using
    // keys from source and destination object
{
    NSArray *relationships = [self componentRelationships];
    EORelationship *sourceToLink = [relationships objectAtIndex:0];
    EORelationship *linkToDestination = [relationships objectAtIndex:1];
    EOEntity *linkEntity = [sourceToLink destinationEntity];
    id link = [linkEntity createObject];
    [sourceToLink setJoinPropertiesInDestinationObject:link fromSourceObject:master];
    [linkToDestination setJoinPropertiesInSourceObject:link fromDestinationObject:destination];
    return link;
}

- updateKeysForSourceObject:source destinationObject:destination
    // Sets keys on source or destination to maintain consistency for
    // the source to now map to destination for this relationship.
    // Does not actually assign destination to ivar in source (just messes
    // with the keys).
    // Returns the modified object (source or destination). In the case of
    // a many to many returns a newly created link object.
    // The caller is reposible for inserting or updating the returned
    // object in the database.
{
    id modifiedObject = nil;
    
    if ([self isToMany]) {
        // figure out what values we need to snarf from our parents
        if ([self isFlattened]) {
            // Many to many   create link obj          Delete link   none     to-many
            // I'm assuming there's only one link object...
            modifiedObject = [self linkObjectForSourceObject:source destinationObject:destination];
        } else {
            // PK -> FK       D->FK := M->PK           none          none     to-many
            [self setJoinPropertiesInDestinationObject:destination fromSourceObject:source];
            modifiedObject = destination;
        }
    } else {
        if ([self hasJoinsOnPrimaryKeyOfSourceEntity]) {
            // PK -> PK       D->PK := M->PK           None          none     to-one
            [self setJoinPropertiesInDestinationObject:destination fromSourceObject:source];
            modifiedObject = destination;
        } else {
            // FK -> PK       M->FK := D->PK           M->FK=NULL    none     to-one
            // We need to update the master with a new foreign key.
            // send it to the dataSource as well...
            [self setJoinPropertiesInSourceObject:source fromDestinationObject:destination];
            modifiedObject = source;
        }
    }
    return modifiedObject;
}


- updateKeysForDeleteOfDestinationObject:destination fromSourceObject:source
    // Sets keys on source or destination to maintain consistency for
    // a delete of the destination from the source.  Returns the object
    // modified, or a synthesized link object that must be deleted to
    // affect the delete.  The caller is responsible for updating the
    // source object, or deleting the destination or link in the database.
    //
    // This does not actually update destination to ivar in source (just messes
    // with the keys).
{
    id modifiedObject = nil;

    if ([self isToMany]) {
        // figure out what values we need to snarf from our parents
        if ([self isFlattened]) {
            // Many to many   create link obj          Delete link            to-many
            // I'm assuming there's only one link object...
            modifiedObject = [self linkObjectForSourceObject:source destinationObject:destination];
        } else {
            // PK -> FK       D->FK := M->PK           Delete D               to-many
            // Nil out foreign key in destination
            [self setJoinPropertiesInDestinationObject:destination fromSourceObject:nil];
            modifiedObject = destination;
        }
    } else {
        if ([self hasJoinsOnPrimaryKeyOfSourceEntity]) {
            // PK -> PK       D->PK := M->PK           delete D               to-one
            // no use nilling out destinations primary key.  Row must be deleted
            modifiedObject = destination;
        } else {
            // FK -> PK       M->FK := D->PK           M->FK=NULL             to-one
            // nil out foreign key in source
            [self setJoinPropertiesInSourceObject:source fromDestinationObject:nil];
            modifiedObject = source;
        }
    }
    return modifiedObject;
}

@end


